/*
 * =====================================================================================
 * Copyright (C) 2024 Ingenic Semiconductor Co.,Ltd
 * All Rights Reserved
 *
 * Filename     : serial.c
 * Author       : Keven <keven.ywhan@ingenic.com>
 * Created      : 2024/06/04 16:24
 * Description  :
 *
 * =====================================================================================
 */

#include "common/reg_base.h"
#include "common/reg_uart.h"

#define APB_BUS_RATE           (24000000)
#define UART_BAUDRATE          (115200)

static void serial_setbrg(struct jz_uart *uart)
{
	uint32_t baud_div, tmp;
	baud_div = APB_BUS_RATE / 16 / UART_BAUDRATE;

	tmp = readl(&uart->lcr);
	tmp |= UART_LCR_DLAB;
	writel(tmp, &uart->lcr);

	writel((baud_div >> 8) & 0xff, &uart->dlhr_ier);
	writel(baud_div & 0xff, &uart->rbr_thr_dllr);

	tmp &= ~UART_LCR_DLAB;
	writel(tmp, &uart->lcr);
}

static int32_t serial_tstc(struct jz_uart *uart)
{
	if (readl(&uart->lsr) & UART_LSR_DR)
		return 1;

	return 0;
}

int32_t serial_init(int32_t uart_idx)
{
	struct jz_uart *uart = (struct jz_uart *)(UART0_BASE + uart_idx * 0x1000);

	/* Disable port interrupts while changing hardware */
	writel(0, &uart->dlhr_ier);

	/* Disable UART unit function */
	writel(~UART_FCR_UUE, &uart->iir_fcr);

	/* Set both receiver and transmitter in UART mode (not SIR) */
	writel(~(SIRCR_RSIRE | SIRCR_TSIRE), &uart->isr);

	/*
	 * Set databits, stopbits and parity.
	 * (8-bit data, 1 stopbit, no parity)
	 */
	writel(UART_LCR_WLEN_8 | UART_LCR_STOP_1, &uart->lcr);

	/* Set baud rate */
	serial_setbrg(uart);

	/* Enable UART unit, enable and clear FIFO */
	writel(UART_FCR_UUE | UART_FCR_FE | UART_FCR_TFLS | UART_FCR_RFLS,
	       &uart->iir_fcr);

	return 0;
}

void serial_putc(int uart_idx, const char c)
{
	struct jz_uart *uart = (struct jz_uart *)(UART0_BASE + uart_idx * 0x1000);

	if (c == '\n')
		serial_putc(uart_idx, '\r');

	writel((uint8_t)c, &uart->rbr_thr_dllr);

	/* Wait for fifo to shift out some bytes */
	while (!((readl(&uart->lsr) & (UART_LSR_TDRQ | UART_LSR_TEMT)) == 0x60))
		;
}

void serial_puts(int32_t uart_idx, const char *s)
{
	while (*s) {
		if (*s == '\n') {
			serial_putc(uart_idx, '\r');
		}
		serial_putc(uart_idx, *s);
		s++;
	}
}

int32_t serial_getc(int32_t uart_idx)
{
	struct jz_uart *uart = (struct jz_uart *)(UART0_BASE + uart_idx * 0x1000);

	while (!serial_tstc(uart))
		;
	return readl(&uart->rbr_thr_dllr);
}
